use uuid::Uuid;

use crate::dbs::sqlite::Peer;
use crate::features::ddd::Aggregate;
use crate::features::gnode;
use crate::features::quizzes::modelsdbo::{QuizDBO, QuizItemDBO};
use crate::features::sgraph::modelsdbo::EdgeDBO;
use crate::features::tasks::modelsdbo::TaskDBO;
use crate::features::tasks::{
  EBP_DIFF_INITIAL, EBP_PROBA_MIN,
};
use chrono::prelude::*;
use derivative::Derivative;
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Clone, Derivative, Debug)]
#[derivative(Default)]
pub struct QuizConfig {
  pub size: usize,
}

#[derive(Serialize, Deserialize, Clone, Derivative, Debug)]
#[derivative(Default)]
pub struct QuizItemConfig {
  pub size: usize,
}

/// events for Quiz Entity
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum QuizEvent {
  Task(TaskCreated),
  ItemTask(ItemTaskCreated),
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct TaskCreated {
  pub quiz_id: String,
  pub user_id: String,
  pub timestamp: String, // HLC format
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ItemTaskCreated {
  pub quiz_item_id: String,
  pub user_id: String,
  pub subject: String,
  pub task_id: String,
  pub timestamp: String, // HLC format
  pub ebp_difficulty: usize,
  pub ebp_forgetproba: usize,
  pub ebp_recallinterval: i64,
}

impl QuizEvent {
  pub fn build_task_created(quiz_id: &str, user_id: &str, timestamp: &str) -> QuizEvent {
    QuizEvent::Task(TaskCreated {
      quiz_id: quiz_id.to_owned(),
      user_id: user_id.to_owned(),
      timestamp: timestamp.to_owned(),
    })
  }
  pub fn build_item_task_created(
    quiz_item_id: &str,
    user_id: &str,
    subject: &str,
    task_id: &str,
    timestamp: &str,
    ebp_difficulty: usize,
    ebp_forgetproba: usize,
    ebp_recallinterval: i64,
  ) -> QuizEvent {
    QuizEvent::ItemTask(ItemTaskCreated {
      quiz_item_id: quiz_item_id.to_owned(),
      user_id: user_id.to_owned(),
      subject: subject.to_owned(),
      task_id: task_id.to_owned(),
      timestamp: timestamp.to_owned(),
      ebp_difficulty: ebp_difficulty.to_owned(),
      ebp_forgetproba: ebp_forgetproba.to_owned(),
      ebp_recallinterval: ebp_recallinterval.to_owned(),
    })
  }
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct QuizAggregate {
  id: String,
  snapshot: QuizSnapshot,
  config: QuizConfig,
}

#[derive(Serialize, Deserialize, Clone, Derivative, Debug)]
#[derivative(Default)]
pub struct QuizSnapshot {
  pub quiz: QuizDBO,
  pub quiz_items: Vec<QuizItemDBO>,
  pub edges: Vec<EdgeDBO>,
  pub tasks: Vec<TaskDBO>,
}

/// commands handler
impl QuizAggregate {
  /// command create task for user
  pub fn create_task(
    &self,
    // my peer
    my_peer: &Peer,
    subject: &str,
  ) -> Result<Vec<QuizEvent>, String> {
    let mut events: Vec<QuizEvent> = Vec::new();
    events.push(QuizEvent::build_task_created(
      &self.id,
      &my_peer.login_user_id,
      &my_peer.timestamp(),
    ));

    for item in &self.snapshot.quiz_items {
      let task_id = Uuid::new_v4().to_simple().to_string();
      let task_id = format!("{}:{}", gnode::IDPrefix::TASK, &task_id);

      let evt = QuizEvent::build_item_task_created(
        &item.id,
        &my_peer.login_user_id,
        subject,
        &task_id,
        &my_peer.timestamp(),
        EBP_DIFF_INITIAL,
        EBP_PROBA_MIN,
        5,// 5 seconds interval
      );
      events.push(evt);
    }

    Ok(events)
  }
}

impl Aggregate for QuizAggregate {
  type ID = String;
  type ItemEvent = QuizEvent;
  type Snapshot = QuizSnapshot;
  type Config = QuizConfig;

  fn init(snapshot: &QuizSnapshot, events: &Vec<QuizEvent>, config: &QuizConfig) -> QuizAggregate {
    let quiz_id = snapshot.quiz.id.clone();

    let mut quiz_agg = QuizAggregate {
      id: quiz_id,
      snapshot: snapshot.to_owned(),
      config: config.to_owned(),
    };

    for evt in events {
      quiz_agg = quiz_agg.apply(evt);
    }
    quiz_agg
  }

  fn apply(&self, evt: &QuizEvent) -> QuizAggregate {
    let now: DateTime<Utc> = Utc::now(); // e.g. `2014-11-28T12:45:59.324310806Z`
    match evt {
      QuizEvent::Task(task_created) => {
        let edge_dbo = EdgeDBO {
          subject: task_created.user_id.clone(),
          predicate: "tester".into(),
          object: task_created.quiz_id.clone(),
          facet: "facet-node-id".into(),
          ..Default::default()
        };
        let mut snapshot = self.snapshot.clone();
        snapshot.edges.push(edge_dbo);

        QuizAggregate {
          snapshot: snapshot,
          ..self.to_owned()
        }
      }
      QuizEvent::ItemTask(item_task_created) => {
        let task_dbo = TaskDBO {
          id: item_task_created.task_id.to_owned(),
          snapshot_at: item_task_created.timestamp.to_owned(),
          quiz_item_id: item_task_created.quiz_item_id.to_owned(),
          assignee_id: item_task_created.user_id.to_owned(),
          subject: item_task_created.subject.to_owned(),

          ebp_difficulty: item_task_created.ebp_difficulty,

          // refer to config table: task_diff_proba for probability 
          ebp_forgetproba: item_task_created.ebp_forgetproba,
          ebp_recalltime: now.timestamp() + item_task_created.ebp_recallinterval,

          // local created datetime
          created_at: now.timestamp(),
          // local updated datetime
          updated_at: now.timestamp(),
          ..Default::default()
        };
        let mut snapshot = self.snapshot.clone();
        snapshot.tasks.push(task_dbo);

        QuizAggregate {
          snapshot: snapshot,
          ..self.to_owned()
        }
      }
    }
  }
  fn get_id(&self) -> &String {
    &self.id
  }
  fn get_snapshot(&self) -> &QuizSnapshot {
    &self.snapshot
  }
}

/// events for QuizItem Entity
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum QuizItemEvent {
  ItemSampleCreated(ItemSampleCreated),
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ItemSampleCreated {
  pub quiz_item_id: String,
  pub user_id: String,
  pub task_id: String,
}

impl QuizItemEvent {
  pub fn build_item_sample_created(
    quiz_item_id: &str,
    user_id: &str,
    task_id: &str,
  ) -> QuizItemEvent {
    QuizItemEvent::ItemSampleCreated(ItemSampleCreated {
      quiz_item_id: quiz_item_id.to_owned(),
      user_id: user_id.to_owned(),
      task_id: task_id.to_owned(),
    })
  }
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct QuizItemAggregate {
  id: String,
  snapshot: QuizItemSnapshot,
  events: Vec<QuizItemEvent>,
  config: QuizItemConfig,
}

#[derive(Serialize, Deserialize, Clone, Derivative, Debug)]
#[derivative(Default)]
pub struct QuizItemSnapshot {
  quiz: Option<QuizItemDBO>,
  edge: Vec<EdgeDBO>,
}
/// commands handler
impl QuizItemAggregate {
  /// command create task for user
  pub fn create_task(
    &self,
    // my peer
    my_peer: &Peer,
    // quiz id
    quiz_id: &str,
  ) -> Result<Vec<QuizItemEvent>, String> {
    Ok(vec![QuizItemEvent::build_item_sample_created(
      quiz_id,
      &my_peer.login_user_id,
      "task2342",
    )])
  }
}

impl Aggregate for QuizItemAggregate {
  type ID = String;
  type ItemEvent = QuizItemEvent;
  type Snapshot = QuizItemSnapshot;
  type Config = QuizItemConfig;

  fn init(
    snapshot: &QuizItemSnapshot,
    events: &Vec<QuizItemEvent>,
    config: &QuizItemConfig,
  ) -> QuizItemAggregate {
    QuizItemAggregate {
      id: "quiz:123".into(),
      snapshot: snapshot.to_owned(),
      events: events.to_owned(),
      config: config.to_owned(),
    }
  }

  fn apply(&self, evt: &QuizItemEvent) -> QuizItemAggregate {
    QuizItemAggregate {
      id: "quiz:123".into(),
      ..self.to_owned()
    }
  }
  fn get_id(&self) -> &String {
    &self.id
  }
  fn get_snapshot(&self) -> &QuizItemSnapshot {
    &self.snapshot
  }
}
